/*
Eteria IRC Client, an RFC 1459 compliant client program written in Java.
Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.Vector;
import ar.com.jkohen.irc.Channel;
import ar.com.jkohen.awt.event.ChatPanelListener;

import ar.com.jkohen.irc.RFC1459;
import ar.com.jkohen.util.Resources;
import ar.com.jkohen.util.CollatedHashtable;

public class ChatTabs extends Canvas implements MouseListener, MouseMotionListener
{
    public static final int NORMAL = 0;
    public static final int CURRENT = 1;
    public static final int ALARM = 2;
	
    public static final int STATES = 3;

    public static final int LEFT = 0;
    public static final int CENTER = 1;
    public static final int COMPLETE = 2;
    
    public static final int POSITIONS = 3;
	
    public static final int PRIV = 0;
    public static final int CHAN = 1;
    public static final int SERV = 2;
	
    public static final int SOURCES = 3;

    private ActionListener actionListener;

    private Image image;
    private Graphics img_gfx;
    private boolean dirty_tabs;
    private boolean dirty_images;

    private CollatedHashtable name_to_tab;
    private Vector public_tabs;
    private Vector private_tabs;
	private Vector service_tabs;

    // Index of the tab displayed on the left margin.
    private int first_displayed;
    private Tab current_visible;

    private Image images[][][];
	
	private EIRC eirc;
	
    public ChatTabs(EIRC eirc)
	{
		this.public_tabs = new Vector();
		this.private_tabs = new Vector();
		this.service_tabs = new Vector();
		this.name_to_tab = new CollatedHashtable(RFC1459.getCollator());

		this.images = new Image[ChatTabs.SOURCES][ChatTabs.STATES][ChatTabs.POSITIONS];
		
		this.eirc = eirc;
		
		addMouseListener(this);
		addMouseMotionListener(this);
    }

    public void setImage(int source, int state, int pos, Image img)
	{
		this.images[source][state][pos] = img;

		boolean changed_size = false;

		int height = img.getHeight(this);
//		while ((height = img.getHeight(this)) == -1);

		// Use maximum height as a reference.
		if (height > Tab.HEIGHT)
		{
	    	Tab.HEIGHT = height;
	    	changed_size = true;
		}

		Image [] state_images = images[source][state];

		if (state != CENTER && state_images[LEFT] != null)
		{
	    	int width_left = state_images[LEFT].getWidth(this);

//		    while ((width_left = state_images[LEFT].getWidth(this)) == -1);

		    int width_sides = width_left;

		    // Use maximum width as a reference.
		    if (Tab.WIDTH - width_sides > Tab.CENTER_WIDTH)
			{
				Tab.CENTER_WIDTH = Tab.WIDTH - width_sides;
				changed_size = true;
	    	}
		}

		if (changed_size)
		    this.dirty_tabs = true;

		this.dirty_images = true;
		repaint();
    }

    public void add(String name)
	{
		Tab tab = new Tab(name);

		if (Channel.isChannel(name))
		{
	    	public_tabs.addElement(tab);
		}
		else
		{
			private_tabs.addElement(tab);
		}
		
		if (eirc.isService(name))
		{
    		tab.setService(true);
		}

		name_to_tab.put(name, tab);

		this.dirty_tabs = true;
		repaint();
    }

    public void remove(String name)
	{
		Tab tab = (Tab) name_to_tab.remove(name);

		if (tab == null) return;

		if (tab.equals(current_visible))
		{
	    	this.current_visible = null;
		}

    	if (Channel.isChannel(name))
    	{
    	    removeTab(public_tabs, tab);
    	}
    	else
    	{
			removeTab(private_tabs, tab);
    	}

    	this.dirty_tabs = true;
    	repaint();
    }

    private void removeTab(Vector v, Tab t)
	{
		int index = v.indexOf(t);

		v.removeElementAt(index);

    	if (t.getImageIndex() == CURRENT && 0 != v.size())
    	{
    	    if (index > 0)
    			index--;

    	    Tab reverted = (Tab) v.elementAt(index);
    	    reverted.setImageIndex(CURRENT);
    	}
    }

    public void rename(String old_name, String name)
	{
		// FIXME: Must use an atomic operator.
		Tab tab = (Tab) name_to_tab.get(old_name);
		tab.setName(name);
		name_to_tab.put(name, tab);
		name_to_tab.remove(old_name);

		this.dirty_tabs = true;
		repaint();
    }

    public void setAlarm(String name, boolean alarm)
	{
		Tab tab = (Tab) name_to_tab.get(name);

		int current_index = tab.getImageIndex();
		int new_index = tab.equals(current_visible) ? ChatTabs.CURRENT : alarm ? ChatTabs.ALARM : ChatTabs.NORMAL;
		
		if (current_index != new_index)
		{
	    	tab.setImageIndex(new_index);
	    	repaint();
		}
    }

    public String getCurrent()
	{
		for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
		{
	    	Tab tab = (Tab) e.nextElement();
	    	if (tab.getImageIndex() == CURRENT)
				return tab.getName();
	    }

		return null;
    }

    public void shiftLeft()
	{
		if (first_displayed > 0)
		{
	    	this.first_displayed--;
	    	repaint();
		}
    }

    public void shiftRight()
	{
		int visible_tabs = getSize().width / Tab.WIDTH;
		int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());

		if (first_displayed < tabs_in_longest_row - visible_tabs)
		{
	    	this.first_displayed++;
	    	repaint();
		}
    }

    public void makeVisible(String name)
	{
		Tab t = (Tab) name_to_tab.get(name);
		if (t != null)
		{
			makeVisible(t);
		}
    }

    protected void makeVisible(Tab new_visible)
	{
		if (new_visible == null)
			return;
			
		int visible_tabs = getSize().width / Tab.WIDTH;
		Vector v = Channel.isChannel(new_visible.getName()) ? public_tabs : private_tabs;
		int tabs_in_row = v.size();
		int index = v.indexOf(new_visible);

		if (tabs_in_row <= visible_tabs)
		{
	    	this.first_displayed = 0;
	    }
		else
		{
			if (tabs_in_row - index >= visible_tabs)
	    		this.first_displayed = index;
			else
	    		this.first_displayed = tabs_in_row - visible_tabs;
		}

		if (current_visible != null)
		    current_visible.setImageIndex(ChatTabs.NORMAL);

		new_visible.setImageIndex(ChatTabs.CURRENT);

		current_visible = new_visible;
		repaint();
    }
    
    public void notVisible()
    {
    	if (current_visible != null)
		    current_visible.setImageIndex(ChatTabs.NORMAL);
		current_visible = null;
		repaint();
    }

    public String getVisible()
	{
		if (current_visible != null)
			return (current_visible.getName());
		else
			return (null);
    }

    public String getTabAt(Point p)
	{
		p.translate(first_displayed * Tab.WIDTH, 0);

		Vector v = p.y < Tab.HEIGHT ? private_tabs : public_tabs;
		int index = p.x / Tab.WIDTH;

		if (index < v.size())
		    return ((Tab) v.elementAt(index)).getName();

		return null;
    }

    protected void adjustTabsTitle(Graphics g)
	{
		FontMetrics fm = g.getFontMetrics();

    	for (Enumeration e = name_to_tab.elements(); e.hasMoreElements(); )
    	{
    	    Tab tab = (Tab) e.nextElement();
    	    String name = tab.getName();
    	    String title = name;
    	    int title_length = title.length();
    	    // Trim tab's title if the name's too long.
			while (fm.stringWidth(title) > Tab.CENTER_WIDTH)
				title = name.substring(0, --title_length).concat("...");

    	    tab.setTitle(title);
    	}
    }

    private void cacheImages()
	{
		for (int j = 0; j < SOURCES; j++)
		{
    		for (int i = 0; i < STATES; i++)
    		{
    	    	Image state_images[] = images[j][i];

    		    int width_left = state_images[LEFT].getWidth(this);

    		    // Build and cache the complete image.
    		    Image image = createImage(Tab.WIDTH, Tab.HEIGHT);
    		    Graphics g = image.getGraphics();

    		    g.drawImage(state_images[LEFT], 0, 0, this);
    		    g.drawImage(state_images[CENTER], width_left, 0, Tab.CENTER_WIDTH, Tab.HEIGHT, this);

    		    g.dispose();

    		    state_images[COMPLETE] = image;
    		}
		}
    }

    private void createImageBuffer(Dimension size)
	{
		this.image = createImage(size.width, size.height);
		if (null != img_gfx)
			img_gfx.dispose();

		this.img_gfx = image.getGraphics();
    }
	
	public void setFont(Font f)
	{
		super.setFont(f);
		repaint();
	}
	
	public void setBackground(Color c)
	{
		super.setBackground(c);
		this.dirty_images = true;
		repaint();
	}
	
    public void update(Graphics g)
	{
		paint(g);
    }

    public void paint(Graphics g)
	{
		Dimension size = getSize();

		if (null == img_gfx || size.height != image.getHeight(this) || size.width != image.getWidth(this))
	    	createImageBuffer(size);

    	if (dirty_tabs)
    	{
    	    this.dirty_tabs = false;
    	    adjustTabsTitle(img_gfx);
    	}

    	if (dirty_images)
    	{
    	    this.dirty_images = false;
    	    cacheImages();
    	}

    	img_gfx.setColor(getBackground());
    	img_gfx.fillRect(0, 0, size.width, size.height);
    	img_gfx.setColor(getForeground());

		img_gfx.setFont(getFont());
    	FontMetrics fm = img_gfx.getFontMetrics();
    	int baseline = fm.getMaxDescent();
    	int text_height = fm.getMaxAscent() + fm.getLeading() + baseline;

    	Vector [] v = { private_tabs, public_tabs };

    	for (int i = 0; i < v.length; i++)
    	{
    	    int x = -first_displayed * Tab.WIDTH;
    	    int y = i * Tab.HEIGHT;
			int source = i;
			
    	    for (Enumeration e = v[i].elements(); e.hasMoreElements(); )
    		{
    			Tab tab = (Tab) e.nextElement();
    			int state = tab.getImageIndex();

    			// Render the tab's background.
      			img_gfx.drawImage(images[(tab.isService() ? SERV : source)][state][ChatTabs.COMPLETE], x, y, this);

    			// Render the title centered on the CENTER image.
      			int width_left = images[(tab.isService() ? SERV : source)][state][ChatTabs.LEFT].getWidth(this); 

    			String title = tab.getTitle();
    			int text_width = fm.stringWidth(title);

    			img_gfx.drawString(title, x - (state != CURRENT ? 1 : 0) + width_left + (Tab.CENTER_WIDTH - text_width) / 2, y - (state != CURRENT ? 1 : 0) + Tab.HEIGHT - baseline - (Tab.HEIGHT - text_height) / 2);

    			x += Tab.WIDTH;
    	    }
    	}

    	g.drawImage(image, 0, 0, this);	
    }

    public Dimension getMinimumSize()
	{
		return getPreferredSize();
    }

    public Dimension getPreferredSize()
	{
		int tabs_in_longest_row = Math.max(public_tabs.size(), private_tabs.size());
		return new Dimension(tabs_in_longest_row * Tab.WIDTH, 2 * Tab.HEIGHT);
    }

    public void setBounds(int x, int y, int w, int h)
	{
		super.setBounds(x, y, w, h);

		if (null != image && (h != image.getHeight(this) || w != image.getWidth(this)))
		    createImageBuffer(new Dimension(w, h));

		makeVisible(current_visible);
    }

    public void addActionListener(ActionListener l)
	{
		if (null != l)
	    	actionListener = AWTEventMulticaster.add(actionListener, l);
    }

    public void removeActionListener(ActionListener l)
	{
		if (null != l)
		    actionListener = AWTEventMulticaster.remove(actionListener, l);
    }

    protected void processActionEvent(ActionEvent e)
	{
		if (actionListener != null)
		    actionListener.actionPerformed(e);
    }

    public void mousePressed(MouseEvent e)
	{
    }

    public void mouseReleased(MouseEvent e)
	{
		if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0 || e.getModifiers() == 0)
		{
			// WORKAROUND: #32 Netscape Navigator doesn't set it right for BUTTON1.
	    	Point p = e.getPoint();

	  		String tab = getTabAt(p);
	    	if (tab != null)
				processActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, tab));

	    	// Hack to avoid getting mouse focus, and therefore take keyboard focus from other components.
	    	e.consume();
		}
    }

    public void mouseClicked(MouseEvent e)
	{
    }
	
    public void mouseDragged(MouseEvent e)
	{
    }
	
    public void mouseMoved(MouseEvent e)
	{
    	Point p = e.getPoint();

  		String tab = getTabAt(p);
    	if (tab != null)
			setCursor(new Cursor(Cursor.HAND_CURSOR));
		else
			setCursor(new Cursor(Cursor.DEFAULT_CURSOR));	
    }
	
    public void mouseEntered(MouseEvent e)
	{
    	Point p = e.getPoint();

  		String tab = getTabAt(p);
    	if (tab != null)
			setCursor(new Cursor(Cursor.HAND_CURSOR));
		else
			setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }
	
    public void mouseExited(MouseEvent e)
	{
		setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }
}

class Tab
{
    static int HEIGHT = 0;
    final static int WIDTH = 96;
    static int CENTER_WIDTH = 0;

    private String name;
    private String title;
    private int image_index;
	private boolean service = false;

    public Tab(String name)
	{
		this.name = this.title = name;
		this.image_index = ChatTabs.NORMAL;
    }

    public String toString()
	{
		return getClass().getName().concat("[name=").concat(name).concat(",title=").concat(title).concat(",state=").concat(String.valueOf(image_index)).concat("]");
    }

    public String getName()
	{
		return name;
    }

    public void setName(String name)
	{
		this.name = name;
    }

    public String getTitle()
	{
		return title;
    }

    public void setTitle(String title)
	{
		this.title = title;
    }

    public int getImageIndex()
	{
		return image_index;
    }

    public void setImageIndex(int image_index)
	{
		this.image_index = image_index;
    }
	
	public void setService(boolean service)
	{
		this.service = service;
	}
	
	public boolean isService()
	{
		return(service);
	}
}
